/*
 * Ethan Levine
 * 
 * The contents of this file are subject to the GNU General Public
 * License (GPL) v2.  The full contents of this license can be
 * found at Mathma's website, http://www.codeplex.com/mathma/license
 */

package mathma.core.form;

import mathma.core.expr.Variable;
import mathma.core.expr.Expression;
import mathma.core.expr.ExpressionType;
import mathma.core.expr.VariableMapping;

import mathma.core.value.Value;
import mathma.core.value.ValueType;

import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;

/**
 * @since 0.1.0
 * @author Ethan Levine
 */
public abstract class FormulaicExpression<E extends Value, O extends Value>
        extends Expression<E> implements Iterable<Expression<O>> {
    
    protected FormulaicExpression(ValueType<E> type) {
        super(type, ExpressionType.FORMULAIC);
    }

    abstract public List<Expression<O>> getOperands();

    abstract public FormulaicExpression<E, O> newInstance(List<Expression<O>> operands);

    public Expression<E> calculate() throws UnknownSuspensionException {
        List<Expression<O>> operands = new ArrayList<Expression<O>>();
        for (Expression<O> operand : getOperands()) {
            if (operand.getExpressionType().equals(ExpressionType.FORMULAIC)) {
                operands.add(((FormulaicExpression) operand).calculate());
            } else {
                operands.add(operand);
            }
        }
        return newInstance(operands);
    }

    public Expression<E> weakCalculate() {
        try {
            return calculate();
        } catch (UnknownSuspensionException e) {
            return this;
        }
    }
    
    public FormulaicExpression<E, O> applyMapping(VariableMapping<O> mapping) {
        List<Expression<O>> operands = new ArrayList<Expression<O>>();
        for (Expression<O> operand : getOperands()) {
            if (operand.getExpressionType().equals(ExpressionType.FORMULAIC)) {
                operands.add(((FormulaicExpression) operand).applyMapping(mapping));
            } else if (operand.getExpressionType().equals(ExpressionType.LITERAL)) {
                operands.add(operand);
            } else if (operand.getExpressionType().equals(ExpressionType.SYMBOLIC)) {
                mapping.mapVariable((Variable<O>) operand);
            }
        }
        return newInstance(operands);
    }

    public boolean isSuspended() {
        return SuspendedExpression.class.isAssignableFrom(this.getClass());
    }

    public boolean containsSuspended() {
        if (isSuspended()) {
            return true;
        } else {
            List<Expression<O>> operands = getOperands();
            for (Expression<O> operand : operands) {
                if (operand.getExpressionType().equals(ExpressionType.FORMULAIC)
                        && ((FormulaicExpression) operand).isSuspended()) {
                    return true;
                }
            }
            return false;
        }
    }

    final public Expression<E> mapVariable(Variable<O> var, Expression<O> value) {
        return this.applyMapping(new VariableMapping<O>(var, value));
    }

    final public FormulaicExpression<E, O> flowExpression(
            FormulaicExpression<O, O> expr, Variable<O> var) {
        List<Expression<O>> newOperands = new ArrayList<Expression<O>>();
        for (Expression<O> operand : getOperands()) {
            newOperands.add(expr.mapVariable(var, operand));
        }
        return newInstance(newOperands);
    }

    public Iterator<Expression<O>> iterator() {
        return getOperands().iterator();
    }
}
